#include "OglWidget.hpp"
#include "3D/TerrainFactory.hpp"
#include "3D/FPSCamera.hpp"
#include "3D/TrackCamera.hpp"
#include "3D/ObjModel.hpp"
#include <QMessageBox>
#include <cmath>

OglWidget::OglWidget(QGLFormat format, QWidget *parent) throw (std::exception):
    QGLWidget(format, parent),
    _scene(NULL),
    _camera(NULL),
    _lastXPosition(-1),
    _lastYPosition(-1),
    _clickedObject(NULL),
    _clickedVertex(NULL),
    _nbClickedVertex(1),
    _currentTexture(NULL)
{
    connect(&_timer, SIGNAL(timeout()), this, SLOT(update()));
    _timer.start(50);
    _time.start();

    this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    this->setFocusPolicy(Qt::ClickFocus);

    for (unsigned int itButtonState = 0; itButtonState < BUTTON_NUMBER; itButtonState++)
        _buttonsStates[itButtonState] = false;

    for (unsigned int itKeyState = 0; itKeyState < KEY_NUMBER; itKeyState++)
        _keyStates[itKeyState] = false;
}

void	OglWidget::initializeGL()
{
    try
    {
        _GLEWWrapper.init();
        _mainShader = _shaderFactory.create("3D/Shaders/PhongIllumination.vert", "3D/Shaders/PhongIllumination.frag");
        _colorPickingShader = _shaderFactory.create("3D/Shaders/ColorPicking.vert", "3D/Shaders/ColorPicking.frag");

        glEnable(GL_DEPTH_TEST);

        _lightPosition.set(0.f, 25.f, -50.f);

    }
    catch (std::exception e)
    {
        QMessageBox::critical(this, "Error", "Failed to create OpenGL context for version 3.1. Check if your graphic card support OpenGL 3.1 and try to update your graphics card drivers.");
    }
}

void	OglWidget::resizeGL(int winWidth, int winHeight)
{
    _projection.loadIdentity();
    _projection.perspective(70, static_cast<float>(winWidth) / static_cast<float>(winHeight), 1.0, 500.0);
    glViewport(0, 0, winWidth, winHeight);
}

OglWidget::~OglWidget()
{
    if (_scene != NULL)
        delete _scene;

    if (_camera != NULL)
        delete _camera;

    std::list<Texture*>::iterator       itTextBegin = _availableTextures.begin();
    const std::list<Texture*>::iterator itTextEnd = _availableTextures.end();

    while (itTextBegin != itTextEnd)
    {
        delete *itTextBegin;
        ++itTextBegin;
    }

    std::list<DrawableObject*>::iterator         itModelBegin = _availableModels.begin();
    const std::list<DrawableObject*>::iterator   itModelEnd = _availableModels.end();

    while (itModelBegin != itModelEnd)
    {
        delete *itModelBegin;
        ++itModelBegin;
    }

    _shaderFactory.destroy(_mainShader);
    _shaderFactory.destroy(_colorPickingShader);
    _GLEWWrapper.destroy();
}

void	OglWidget::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    if (_currentTexture != NULL)
    {
        _currentTexture->enable();
        _currentTexture->bind();
    }

    if (_scene != NULL && _camera != NULL)
    {
        draw(_mainShader);
    }
    if (_currentTexture != NULL)
        _currentTexture->disable();

    glFlush();
    swapBuffers();
}

void    OglWidget::draw(Shader& shader)
{
    animate(_time.elapsed());
    _time.restart();

    _modelView.loadIdentity();

    Vec3f const&    position = _camera->getPosition();
    Vec3f const&    lookedPoint = _camera->getLookedPoint();
    Vec3f const&    orientation = _camera->getOrientation();

    _modelView.lookAt(position[0], position[1], position[2], lookedPoint[0], lookedPoint[1], lookedPoint[2], orientation[0], orientation[1], orientation[2]);

    shader.use();
    shader.sendUniformData("UniMatModelview", _modelView);
    shader.sendUniformData("UniMatProjection", _projection);
    shader.sendUniformData("UniMatNormal", _modelView.normalMat());
    shader.sendUniformData("UniLightPosition", _lightPosition);
    shader.sendUniformData("UniEyePosition", _modelView.eye());

    shader.sendUniformData("UniColorDiffuse", Vec3f(0.8, 0.8, 0.8));
    shader.sendUniformData("UniColorAmbient", Vec3f(0.2, 0.2, 0.2));
    shader.sendUniformData("UniColorSpecular", Vec3f(1.0, 1.0, 1.0));
    shader.sendUniformData("UniRotation", _rot);

    _scene->draw(&shader);

    shader.disable();
}

void    OglWidget::animate(int elapsedTime)
{
    if (_camera != NULL && _cameraType == CAMERA_FPS)
    {
        FPSCamera*   camera = dynamic_cast<FPSCamera*>(_camera);

        if (_keyStates[KEY_W] == true)
            camera->moveForward(elapsedTime);
        else if (_keyStates[KEY_S] == true)
            camera->moveBackward(elapsedTime);
        else if (_keyStates[KEY_A] == true)
            camera->moveLeft(elapsedTime);
        else if (_keyStates[KEY_D] == true)
            camera->moveRight(elapsedTime);
    }
    else if (_camera != NULL && _cameraType == CAMERA_TRACK)
    {
        TrackCamera*    camera = dynamic_cast<TrackCamera*>(_camera);

        _rot.loadIdentity();
        _rot.rotation(camera->getAngleX(), Vec3f(0.f, 1.f, 0.f));
        _rot.rotation(camera->getAngleY(), Vec3f(1.f, 0.f, 0.f));
    }

    if (_clickedObject != NULL)
    {
        if (_keyStates[KEY_UP] == true)
            _clickedObject->translate(0, 0, 1);
        else if (_keyStates[KEY_DOWN] == true)
            _clickedObject->translate(0, 0, -1);
        else if (_keyStates[KEY_LEFT] == true)
            _clickedObject->translate(1, 0, 0);
        else if (_keyStates[KEY_RIGHT] == true)
            _clickedObject->translate(-1, 0, 0);
        else if (_keyStates[KEY_PGUP] == true)
            _clickedObject->translate(0, 1, 0);
        else if (_keyStates[KEY_PGDOWN] == true)
            _clickedObject->translate(0, -1, 0);
    }
}

void    OglWidget::picking(const int clickPosX, const int clickPosY)
{
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    if (_scene != NULL && _camera != NULL)
    {
        draw(_colorPickingShader);
    }
    glFlush();

    GLint           viewPort[4];
    unsigned char   pixels[3];

    glGetIntegerv(GL_VIEWPORT, viewPort);

    glReadPixels(clickPosX, viewPort[3] - clickPosY, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, pixels);


    DrawableObject* clickedObject = _scene->findObject(pixels[0], pixels[1], pixels[2]);

    if (clickedObject != NULL)
    {
        if (_clickedObject != NULL)
        {
            changeVerticesColor(_clickedObject->getVertices(), _clickedObject->getNbVertices(), 1.f, 1.f, 1.f);
            _clickedObject->updateVBO();
        }
        _clickedObject = clickedObject;
        changeVerticesColor(_clickedObject->getVertices(), _clickedObject->getNbVertices(), 1.f, 0.f, 0.f);
        _clickedObject->updateVBO();
    }
    else
    {
        Terrain*    terrain = _scene->getTerrain();

        if (terrain != NULL)
        {
            _clickedVertex = terrain->findVertices(pixels[0], pixels[1], pixels[2], _nbClickedVertex);
            if (_clickedVertex != NULL)
            {
                changeVerticesColor(_clickedVertex, _nbClickedVertex, 1.f, 0.f, 0.f);
                terrain->update();
            }
        }
    }

    glPolygonMode(GL_FRONT_AND_BACK, _drawingMode);
}

void    OglWidget::mousePressEvent(QMouseEvent* event)
{
    if (_scene != NULL && _camera != NULL &&
        _buttonsStates[BUTTON_LEFT] == false && _buttonsStates[BUTTON_RIGHT] == false)
    {
        if ((_cameraType == CAMERA_FPS || _cameraType == CAMERA_TRACK) && event->button() == Qt::LeftButton)
        {
            _buttonsStates[BUTTON_LEFT] = true;
            this->setMouseTracking(true);
            _lastXPosition = event->pos().x();
            _lastYPosition = event->pos().y();
        }
        else if (event->button() == Qt::RightButton)
        {
            _buttonsStates[BUTTON_RIGHT] = true;
            this->setMouseTracking(true);
            _lastXPosition = event->pos().x();
            _lastYPosition = event->pos().y();
            picking(_lastXPosition, _lastYPosition);
        }
    }
}

void    OglWidget::mouseReleaseEvent(QMouseEvent* event)
{
    if (_scene != NULL && _camera != NULL &&
        (_buttonsStates[BUTTON_LEFT] == true || _buttonsStates[BUTTON_RIGHT] == true))
    {
        if (event->button() == Qt::LeftButton)
        {
            _buttonsStates[BUTTON_LEFT] = false;
            this->setMouseTracking(false);
        }
        else if (event->button() == Qt::RightButton)
        {
            if (_clickedVertex != NULL)
            {
                changeVerticesColor(_clickedVertex, _nbClickedVertex, 1.f, 1.f, 1.f);
                _scene->getTerrain()->update();
                delete[] _clickedVertex;
                _clickedVertex = NULL;
            }
            _buttonsStates[BUTTON_RIGHT] = false;
            this->setMouseTracking(false);
        }
    }
}

void    OglWidget::mouseMoveEvent(QMouseEvent *event)
{
    if (_scene != NULL && _camera != NULL)
    {
        QPoint const&   mousePos = event->pos();
        int             xMovement, yMovement;

        xMovement = mousePos.x() - _lastXPosition;
        yMovement = mousePos.y() - _lastYPosition;

        _lastXPosition = mousePos.x();
        _lastYPosition = mousePos.y();

        if (_buttonsStates[BUTTON_LEFT] == true)
        {
            if (_cameraType == CAMERA_FPS)
            {
                FPSCamera*  camera = dynamic_cast<FPSCamera*>(_camera);
                camera->setTheta(camera->getTheta() - xMovement * camera->getSensivity());
                camera->setPhi(camera->getPhi() - yMovement * camera->getSensivity());
                camera->computeVectors();
            }
            else if (_cameraType == CAMERA_TRACK)
            {
                TrackCamera*    camera = dynamic_cast<TrackCamera*>(_camera);
                camera->setAngle(camera->getAngleX() + xMovement * camera->getMotionSensivity(),
                                 camera->getAngleY() + yMovement * camera->getMotionSensivity());
            }
        }
        else if (_buttonsStates[BUTTON_RIGHT] == true)
        {
            if (_clickedVertex != NULL)
            {
                for (unsigned int it = 0; it < _nbClickedVertex * _nbClickedVertex; it++)
                {
                    if (_clickedVertex[it] != NULL)
                        _clickedVertex[it]->posY -= (yMovement / 2.0);
                }
            }
            _scene->update();
        }
    }
}

void	OglWidget::keyPressEvent(QKeyEvent* event)
{
    if (_cameraType == CAMERA_FPS)
    {
        if (event->key() == Qt::Key_W)
            _keyStates[KEY_W] = true;
        else if (event->key() == Qt::Key_S)
            _keyStates[KEY_S] = true;
        else if (event->key() == Qt::Key_A)
            _keyStates[KEY_A] = true;
        else if (event->key() == Qt::Key_D)
            _keyStates[KEY_D] = true;
    }
    if (_clickedObject != NULL)
    {
        if (event->key() == Qt::Key_Up)
            _keyStates[KEY_UP] = true;
        else if (event->key() == Qt::Key_Down)
            _keyStates[KEY_DOWN] = true;
        else if (event->key() == Qt::Key_Left)
            _keyStates[KEY_LEFT] = true;
        else if (event->key() == Qt::Key_Right)
            _keyStates[KEY_RIGHT] = true;
        else if (event->key() == Qt::Key_PageUp)
            _keyStates[KEY_PGUP] = true;
        else if (event->key() == Qt::Key_PageDown)
            _keyStates[KEY_PGDOWN] = true;
    }
}

void	OglWidget::keyReleaseEvent(QKeyEvent* event)
{
    if (event->key() == Qt::Key_W)
        _keyStates[KEY_W] = false;
    else if (event->key() == Qt::Key_S)
        _keyStates[KEY_S] = false;
    else if (event->key() == Qt::Key_A)
        _keyStates[KEY_A] = false;
    else if (event->key() == Qt::Key_D)
        _keyStates[KEY_D] = false;
    else if (event->key() == Qt::Key_W)
        _keyStates[KEY_UP] = false;
    else if (event->key() == Qt::Key_Up)
        _keyStates[KEY_UP] = false;
    else if (event->key() == Qt::Key_Down)
        _keyStates[KEY_DOWN] = false;
    else if (event->key() == Qt::Key_Left)
        _keyStates[KEY_LEFT] = false;
    else if (event->key() == Qt::Key_Right)
        _keyStates[KEY_RIGHT] = false;
    else if (event->key() == Qt::Key_PageUp)
        _keyStates[KEY_PGUP] = false;
    else if (event->key() == Qt::Key_PageDown)
        _keyStates[KEY_PGDOWN] = false;
}

void    OglWidget::setScene(Scene* scene)
{
    if (_scene != NULL)
        delete _scene;
    _scene = scene;
    if (_scene != NULL)
    {
        Terrain*            terrain = _scene->getTerrain();

        if (terrain != NULL)
        {
            DrawableObject*   terrainDrawable = terrain->getDrawable();

            terrainDrawable->init();
        }
    }
}

Scene*  OglWidget::getScene()
{
    return _scene;
}

void    OglWidget::setCameraType(CameraType cameraType)
{
    if (_camera != NULL)
        delete _camera;
    if (_scene != NULL)
    {
        Terrain*    terrain = _scene->getTerrain();
        int         terrainWidth = 100, terrainHeight = 100;

        if (terrain != NULL)
        {
            terrainWidth = terrain->getWidth();
            terrainHeight = terrain->getHeight();
        }

        _rot.loadIdentity();
        if (cameraType == CAMERA_FIXED)
            _camera = new Camera(Vec3f(-terrainWidth / 4.f, terrainWidth, -terrainHeight / 4.f), Vec3f(0, 0, 0), Vec3f(0, 1, 0));
        else if (cameraType == CAMERA_FPS)
            _camera = new FPSCamera(Vec3f(-terrainWidth / 4.f, 25, -terrainHeight / 4.f), Vec3f(0, 0, 0), Vec3f(0, 1, 0));
        else if (cameraType == CAMERA_TRACK)
            _camera = new TrackCamera(Vec3f(-terrainWidth / 4.f, terrainWidth, -terrainHeight / 4.f), Vec3f(0, 0, 0), Vec3f(0, 1, 0));
        _cameraType = cameraType;
    }
}

void    OglWidget::setBrushType(BrushType brushType)
{
    _brushType = brushType;
    if (_brushType == BRUSH_LITTLE)
        _nbClickedVertex = 1;
    else if (_brushType == BRUSH_MIDDLE)
        _nbClickedVertex = 6;
    else if (_brushType == BRUSH_LARGE)
        _nbClickedVertex = 12;
}

void    OglWidget::setDrawingMode(int drawingMode)
{
    _drawingMode = drawingMode;
    glPolygonMode(GL_FRONT_AND_BACK, _drawingMode);
}

void    OglWidget::addTexture(Texture* texture)
{
    _availableTextures.push_back(texture);
}

void    OglWidget::addModel(Model* model)
{
    model->setShader(&_mainShader);
    model->init();
    _availableModels.push_back(model);

}

void    OglWidget::setCurrentTexture(const std::string &name)
{
    std::list<Texture*>::iterator       itBegin = _availableTextures.begin();
    const std::list<Texture*>::iterator itEnd = _availableTextures.end();

    while (itBegin != itEnd && (*itBegin)->getName() != name)
    {
        ++itBegin;
    }
    if (itBegin != itEnd)
        _currentTexture = *itBegin;
    else
        _currentTexture = NULL;

    if (_scene != NULL)
    {
        Terrain*    terrain = _scene->getTerrain();
        terrain->setTexture(_currentTexture);
    }

}

void    OglWidget::setCurrentModel(unsigned int position)
{
    std::list<DrawableObject*>::iterator         itBegin = _availableModels.begin();
    const std::list<DrawableObject*>::iterator   itEnd = _availableModels.end();
    unsigned int                        it = 0;

    while (itBegin != itEnd && it < position)
    {
        ++it;
        ++itBegin;
    }
    if (itBegin != itEnd)
    {
        if (_scene == NULL)
            _scene = new Scene();
        if (_camera == NULL)
            setCameraType(CAMERA_FIXED);
        ObjModel* found = dynamic_cast<ObjModel*>(*itBegin);
        _scene->addObject(new ObjModel(*found));
    }
}
